define('pc/js/components/Calendar/Calendar', ['pc/css/com-calendar.css'], function(){

/**
 * new XCalendar(...).bindTo(targetElement);
 */
function XCalendar(options) {
    this.doc = document;

    /**
     * element which the calendar bind to
     */
    this.targetElement = null;
    /**
     * calendar wrapper
     */
    this.wrapper = null;
    /**
     * options
     */
    this.options = {
        zIndex: 600,
        format: 'y-m-d h:i',
        onPick: null,
        showTime: true,
        timeDisplayFormat: null,
        timeIntervals: 30
    };

    this.hasMount = false;

    this.initOptions(options);
}
XCalendar.prototype = {
    constructor: XCalendar,
    initDate: function(date) {
        var d = ('' === date || undefined === date)
            ? new Date() : new Date( date.replace(/-/g, '/') );

        this.year = d.getFullYear();
        this.month = d.getMonth() + 1;
        this.date = d.getDate();
        this.hours = d.getHours();
        this.minutes = d.getMinutes();
        this.seconds = d.getSeconds();
    },
    initOptions: function(options) {
        if(undefined === options) {
            return;
        }

        for(var k in options) {
            this.options[k] = options[k];
        }
    },
    lPad: function(str) {
        var ret = String(str);

        while(ret.length < 2) {
            ret = '0' + ret;
        }

        return ret;
    },
    handlerEvent: function(e) {
        var target = e.target || e.srcElement;

        if('I' === target.nodeName.toUpperCase()
            || 'EM' === target.nodeName.toUpperCase()) {

            target = target.parentNode;
        }

        switch(target.getAttribute('data-action')) {
            case 'lastYear':
                this.year--;
                this.render();

                break;
            case 'nextYear':
                this.year++;
                this.render();

                break;
            case 'lastMonth':
                if(1 === this.month) {
                    this.year--;
                    this.month = 12;

                } else {
                    this.month--;
                }
                this.render();

                break;
            case 'nextMonth':
                if(12 === this.month) {
                    this.year++;
                    this.month = 1;

                } else {
                    this.month++;
                }
                this.render();

                break;
            case 'selectYear':
                this.popYear(this.year);

                // show
                this.togglePoplay();

                break;
            case 'pickYear':
                if('1' === target.getAttribute('data-disabled')) {
                    return;
                }

                var year = target.getAttribute('data-value');
                this.year = parseInt(year, 10);
                this.render();

                break;
            case 'selectMonth':
                this.popMonth();

                // show
                this.togglePoplay();

                break;
            case 'pickMonth':
                var month = target.getAttribute('data-value');
                this.month = parseInt(month, 10);
                this.render();

                break;
            case 'pickDay':
                if('1' === target.getAttribute('data-disabled')) {
                    return;
                }

                var str = target.getAttribute('data-value');
                var arr = str.split('-');

                //this.year = parseInt(arr[0], 10);
                this.month = parseInt(arr[1], 10);
                this.date = parseInt(arr[2], 10);

                // 如果没有时间 那么直接填充时间到页面
                // 否则 需要选择时间后再填充页面
                if(this.options.showTime) {
                    this.render();

                } else {
                    this.fillDate();
                    this.unMount();
                }

                break;
            case 'pickTime':
                var str = target.getAttribute('data-value');
                var arr = str.split(':');

                this.hours = parseInt(arr[0], 10);
                this.minutes = parseInt(arr[1], 10);

                this.fillDate();
                this.unMount();

                break;
            default:
                break;
        }
    },
    appendHtml: function(html, firstAppend) {
        if(firstAppend) {
            this.wrapper.innerHTML = html;

            return;
        }

        this.wrapper.innerHTML += html;
    },
    getDaysOfMonth: function(year, month) {
        if(0 === month) {
            year = year - 1;
            month = 12;
        }
        if(13 === month) {
            year = year + 1;
            month = 1;
        }

        if(2 === month) {
            if(0 === year % 400 || (0 === year % 4 && 0 !== year % 100)) {
                return 29;
            }

            return 28;
        }

        if(4 === month || 6 === month || 9 === month || 11 === month) {
            return 30;
        }

        return 31;
    },
    renderHeader: function() {
        var title =
        ['<section class="x-calendar-header">',
            '<table class="x-calendar-table">',
                '<tr>',
                    '<td><label data-action="lastYear">◄</label></td>',
                    '<td><label data-action="selectYear">'+ this.year +'</label></td>',
                    '<td><label data-action="nextYear">►</label></td>',
                    '<td><label data-action="lastMonth">◄</label></td>',
                    '<td><label data-action="selectMonth">'+ this.month +'</label></td>',
                    '<td><label data-action="nextMonth">►</label></td>',
                '</tr>',
            '</table>',
        '</section>'].join('');

        this.appendHtml(title, true);
    },
    renderContent: function() {
        // start
        var content = '<section class="x-calendar-content">';

        // week
        content += this.getWeekString();

        // day
        content += this.getDayString();

        // poplay
        content += '<div class="x-calendar-poplay"></div>';

        // end
        content += '</section>';

        this.appendHtml(content);
    },
    renderTime: function() {
        var html = '<section class="x-calendar-time">';

        // 从选定的时间 或者 当前时间开始循环
        var interval = this.options.timeIntervals;
        var startHour = new Date().getHours();
        var startMinute = 0;
        if(undefined !== this.minutes && 0 === this.minutes % interval) {
            startHour = this.hours;
            startMinute = this.minutes;
        }

        var tmp = '';
        // 后面的时间
        for(var h=startHour; h<24; h++) {
            for(var m=startMinute; m<60; m = m + interval) {
                tmp += '<div class="'+ (this.hours === h && this.minutes === m ? 'active' : '') +'" data-action="pickTime" data-value="'+ h + ':' + m +'">'
                    + ( null === this.options.timeDisplayFormat
                        ? this.lPad(h) + ':' + this.lPad(m)
                        : this.options.timeDisplayFormat(h, m) )
                    + '</div>';
            }

            // 下一个小时的分钟正常从 0 开始
            startMinute = 0;
        }
        // 前面的时间
        for(var h=0; h<startHour; h++) {
            for(var m=startMinute; m<60; m = m + interval) {
                tmp += '<div data-action="pickTime" data-value="'+ h + ':' + m +'">'
                    + ( null === this.options.timeDisplayFormat
                        ? this.lPad(h) + ':' + this.lPad(m)
                        : this.options.timeDisplayFormat(h, m) )
                    + '</div>';
            }
        }

        html += tmp;
        html += '</section>';

        this.appendHtml(html)
    },
    getWeekString: function() {
        var week =
        ['<div class="x-calendar-week">',
            '<table class="x-calendar-table">',
                '<tr>',
                    '<td>日</td>',
                    '<td>一</td>',
                    '<td>二</td>',
                    '<td>三</td>',
                    '<td>四</td>',
                    '<td>五</td>',
                    '<td>六</td>',
                '</tr>',
            '</table>',
        '</div>'].join('');

        return week;
    },
    getDayString: function() {
        var html = '<div class="x-calendar-day"><div data-role="line">';

        var days = this.getDaysOfMonth(this.year, this.month);
        var startWeek = new Date(this.year, this.month - 1, 1).getDay();
        var lastMonthDays = this.getDaysOfMonth(this.year, this.month - 1);
        var nextMonthDays = this.getDaysOfMonth(this.year, this.month + 1);
        // 占满 6 行
        var totalDays = 42;

        // 向上个月补齐
        for(var i=startWeek; i>0; i--) {
            html += '<span data-action="pickDay" data-invalid="1" data-value="'+ this.year + '-' + (this.month - 1) + '-' + (lastMonthDays - i + 1) +'">'
                + (lastMonthDays - i + 1)
                +'</span>';

            totalDays--;

            // 换行
            if(0 === totalDays % 7) {
                html += '</div><div data-role="line">';
            }
        }

        // 本月
        for(var j=1; j<=days; j++) {
            if(j === this.date) {
                html += '<span class="active" data-action="pickDay" data-value="'+ this.year + '-' + this.month + '-' + j +'">'
                    + j +'</span>';

            } else {
                html += '<span data-action="pickDay" data-value="'+ this.year + '-' + this.month + '-' + j +'">'
                    + j +'</span>';
            }

            totalDays--;

            // 换行
            if(0 === totalDays % 7) {
                html += '</div><div data-role="line">';
            }
        }

        // 向下个月补齐
        for(var k=1; k<=nextMonthDays; k++) {
            html += '<span data-action="pickDay" data-invalid="1" data-value="'+ this.year + '-' + (this.month + 1) + '-' + k +'">'
                + k +'</span>';

            totalDays--;

            if(totalDays <= 0) {
                break;
            }

            // 换行
            if(0 === totalDays % 7) {
                html += '</div><div data-role="line">';
            }
        }

        html += '</div></div>';

        return html;
    },
    render: function() {
        this.renderHeader();

        this.renderContent();

        if(this.options.showTime) {
            this.renderTime();
        }
    },
    getPopLay: function() {
        return this.wrapper.querySelector('.x-calendar-poplay');
    },
    togglePoplay: function() {
        var popLay = this.getPopLay();

        if('block' === popLay.style.display) {
            popLay.style.display = 'none';

            return;
        }

        popLay.style.display = 'block';
    },
    popYear: function(startYear) {
        var popLay = this.getPopLay();
        var ret = '';

        for(var i=-4,tmp=''; i<7; i++) {
            tmp = this.year === startYear + i ? 'active' : '';
            ret += '<span class="x-calendar-poplay-item-number '+ tmp +'" data-action="pickYear" data-value="'+ (startYear + i) +'">'+ (startYear + i) +'</span>';
        }

        popLay.innerHTML = ret;
    },
    popMonth: function() {
        var popLay = this.getPopLay();
        var ret = '';

        for(var i=1; i<=12; i++) {
            ret += '<span class="x-calendar-poplay-item-number" data-action="pickMonth" data-value="'+ i +'">'+ i +'</span>';
        }

        popLay.innerHTML = ret;
    },
    fillDate: function() {
        var _self = this;
        var funs = {
            y: function() {
                return _self.year;
            }
            ,m: function() {
                return _self.lPad(_self.month);
            }
            ,d: function() {
                return _self.lPad(_self.date);
            }
            ,h: function() {
                return _self.lPad(_self.hours);
            }
            ,i: function() {
                return _self.lPad(_self.minutes);
            }
            ,s: function() {
                return _self.lPad(_self.seconds);
            }
        };

        var ret = this.options.format.replace(/(.?)/ig, function(match, p/* , offset, string */) {
            return undefined !== funs[match] ?
                funs[match]() :
                p;
        });

        // 填充时间
        this.targetElement.value = ret;

        if(null !== this.options.onPick) {
            this.options.onPick(ret);
        }
    },
    unMount: function() {
        if(!this.hasMount) {
            return;
        }

        this.doc.body.removeChild(this.wrapper);

        this.hasMount = false;
    },
    mount: function() {
        if(this.hasMount) {
            return;
        }
        this.hasMount = true;

        // 定位日期
        this.initDate(this.targetElement.value);

        // pos
        var pos = this.targetElement.getBoundingClientRect();
        this.wrapper.style.top = pos.top + pos.height + 2 + 'px';
        this.wrapper.style.left = pos.left + 'px';

        this.doc.body.appendChild(this.wrapper);
        this.render();
    },
    bindTo: function(targetElement) {
        var _self = this;

        this.targetElement = targetElement;

        // dom
        this.wrapper = this.doc.createElement('div');
        this.wrapper.className = 'x-calendar-wrapper';
        this.wrapper.style.zIndex = this.options.zIndex;

        // event
        this.wrapper.onclick = function(e) {
            e.stopPropagation && e.stopPropagation();

            _self.handlerEvent(e);
        };
        this.targetElement.onclick = function(e) {
            _self.mount();
        };
        this.doc.addEventListener('click', function(e){
            // 非日历内点击 需要关闭日历
            var t = e.target;
            var inCalendar = false;
            var node = t;

            if(_self.targetElement === t) {
                return;
            }

            while(null !== node) {
                if(!node.className || 'BODY' === node.nodeName.toUpperCase()) {
                    inCalendar = false;
                    break;
                }
                if(node.className.indexOf('x-calendar-wrapper') >= 0) {
                    inCalendar = true;
                    break;
                }

                node = node.parentNode;
            }

            if(!inCalendar) {
                _self.unMount();
            }
        });
    }
};

return XCalendar;
});
